/*
Metakosmos
2011 Chad McKinney with Curtis McKinney and Cole Ingraham, as Glich Lich
A piece using a metaverse of SuperCollider tweets.

Metakosmos.new;
*/

// MetaGUI defines the view of the system
MetaGUI {
	
	var mainWindow,<reserveView,<activeView,reserveDictionary,activeDictionary,reserveLabel,activeLabel,mLogic,chimericColor;
	var resetButton,cleanButton;
	
	/*
	MetaGUI Constructor
	@param logic The MetaLogic instance we use to organize the logic of the piece
	*/
	*new { |logic|
		^super.new.init(logic);
	}
	
	init { |logic|
		mLogic = logic;
		chimericColor = Color.rand;
		
		// Main Window
		mainWindow = Window.new("Metakosmos",Rect(225,200,800,600),false);
		mainWindow.view.decorator = FlowLayout.new(mainWindow.view.bounds);
		
		// Reserve View
		reserveView = ScrollView.new(mainWindow,Rect(0,0,792,425))
			.hasBorder_(true)
			.background_(Color.new(0.2,0.2,0.2));
		reserveView.decorator = FlowLayout.new(reserveView.bounds);
		reserveLabel = StaticText.new(reserveView,Rect(0,0,reserveView.bounds.width-109,40))
			.font_(Font("Osaka-mono",16))
			.string_("::Reserve SCTweets::")
			.stringColor_(Color.new(1,1,1,0.4));
		
		// Resets the entire gui/networking
		resetButton = Button.new(reserveView,Rect(0,0,40,20))
			.canFocus_(false)
			.states_([["Reset",Color.black,Color.new(0.5,0.5,0.5)]])
			.action_({ 
				mLogic.reset;
			});
			
		cleanButton = Button.new(reserveView,Rect(0,0,40,20))
			.canFocus_(false)
			.states_([["Clean",Color.black,Color.new(0.5,0.5,0.5)]])
			.action_({ 
				this.clean;
			});
		
		reserveView.decorator.nextLine;
		reserveDictionary = Dictionary.new;
		this.makeReserves;
		
		// Active View
		activeView = ScrollView.new(mainWindow,Rect(0,0,792,165))
			.hasBorder_(true)
			.background_(chimericColor);
		activeView.decorator = FlowLayout.new(activeView.bounds);
		activeLabel = StaticText.new(activeView,Rect(0,0,180,40))
			.font_(Font("Osaka-mono",16))
			.string_("::Active SCTweets::")
			.stringColor_(Color.new(0,0,0));
		activeView.decorator.nextLine;
		activeDictionary = Dictionary.new;
				
		this.generateColor;
		mainWindow.front;
	}
	
	// Reset the GUI
	reset {
		reserveDictionary.collect {
			|item,index|
			item.remove;
		};
		reserveDictionary.clear;
		
		activeDictionary.collect {
			|item,index|
			item.remove;
		};
		activeDictionary.clear;
		
		this.makeReserves;
		this.reflowReserveView;
		this.reflowActiveView;
	}
	
	clean {
		this.reset;
		mLogic.exit;
	}
	
	// Initialize the Tweet source code container, set up the ReserveTweet list, and build the GUI elements
	makeReserves {
		MetaLogic.tweetOrderArray.do{
			|key,index|
			reserveDictionary.add(key->ReserveTweetView.new(this,key,index));
			reserveDictionary[key].setButtonColor(chimericColor);
		}
	}
	
	// Remove a ReserveTweetView. This could be called from the local GUI or OSC because of syncing.
	removeReserve {
		|key,index|
		
		reserveDictionary[key].remove;
		reserveDictionary.removeAt(key);
		this.reflowReserveView;
		/*
		if(reserveDictionary.includes(key),
		{ 
			reserveDictionary[key].remove;
			reserveDictionary.removeAt(key);
			this.reflowReserveView;
		});
		*/
	}
	
	/*
	 This is only used when the network for some reason dropped a packet and our reserve lists are out of sync 
	 and we need to add back a missing reserve item
	*/
	addReserve {
		|key,index|
		if(reserveDictionary.includes(key) == false,
		{
			reserveDictionary.add(key->ReserveTweetView.new(this,key,index));
			reserveDictionary[key].setButtonColor(chimericColor);
			this.reflowReserveView;
			"GUI ADD RESERVE FROM SYNC".postln;
		});
	}
	
	// Reflow the reserveView. We need to do this everytime we remove an element.
	reflowReserveView {
		reserveView.decorator.reset;
		reserveView.children.do({
			|widget|
			if(widget.isKindOf( StartRow ),{
				reserveView.decorator.nextLine
			},{
				reserveView.decorator.place(widget);
			})
		});	
	}
	
	// Give MetaLogic the index of the tweetOrderArray that needs to be sent over the network to active them for the other players
	sendTweet {
		|key='defaultTweet',index=0,action="mono"|
		mLogic.sendTweet(index,action);
		this.makeActive(key,index);
	}
	
	//  Add a single active tweet to te activeDictionary AND remove it from the reserveDictionary
	makeActive { 
		|key='defaultTweet',index=0|
		activeDictionary.add(key->ActiveTweetView.new(this,key,index));
		this.generateColor;
	}
	
	// Remove an ActiveTweetView. This could be called from the local GUI or OSC because of syncing.
	removeActive {
		|key|
		activeDictionary[key].remove;
		activeDictionary.removeAt(key);
		//this.makeActive(key);
		this.reflowActiveView;
	}
	
	// Remove all the active tweets from the GUI
	removeAllActive {
		
		// Then clear the dictionary
		activeDictionary.collect{
			|item,key|
			item.remove(true);
		};
		activeDictionary = Dictionary.new;
		this.reflowActiveView;
	}
	
	// Reflow the activeView. We need to do this everytime we remove an element.
	reflowActiveView {
		activeView.decorator.reset;
		activeView.children.do({
			|widget|
			if(widget.isKindOf( StartRow ),{
				activeView.decorator.nextLine
			},{
				activeView.decorator.place(widget);
			})
		});	
	}
	
	// Generate a set of random color for the GUI pallette
	generateColor {
		{ 
			chimericColor = Color.rand(0.5,0.6);
			reserveDictionary.collect{|item| item.setButtonColor(chimericColor) };
			activeView.background_(chimericColor);
		}.defer;	
	}
	
}


// ReserveTweetView is a class that that represents an unplayed tweet in the GUI
ReserveTweetView {
	
	var <mName,mIndex,tweetView,label,monoButton,polyButton,mParent;
	
	/*
	ReserveTweetView constructor
	@param parent Parent view of this GUI element
	@param name Name of tweet, also used as the key in the Mikrokosm 
	*/
	*new { |parent,name = 'defaultTweet',index=0|
		^super.new.init(parent,name,index);
	}
	
	init { |parent,name = 'defaultTweet',index=0|
		mParent = parent;
		mName = name;
		mIndex = index;
		
		tweetView = FlowView.new(mParent.reserveView,Rect(0,0,mParent.reserveView.bounds.width-18,20))
			.background_(Color.new(1,1,1,0.3));
			
		monoButton = Button.new(tweetView,Rect(0,0,40,20))
			.font_(Font("Osaka-mono",13))
			.states_([["Mono",Color.new(0,0,0),Color.rand(0.4,0.6)]])
			.action_({ this.removeSelf("mono");})
			.canFocus_(false);
			
		polyButton = Button.new(tweetView,Rect(0,0,40,20))
			.font_(Font("Osaka-mono",13))
			.states_([["Poly",Color.new(0,0,0),Color.rand(0.4,0.6)]])
			.action_({ this.removeSelf("poly");})
			.canFocus_(false);
			
		label = StaticText.new(tweetView,Rect(0,0,tweetView.bounds.width-100,20))
			.string_(mName)
			.font_(Font("Osaka-mono",13))
			.stringColor_(Color.new(0,0,0));
	}
	
	setButtonColor { |color|
		monoButton.states_([["Mono",Color.new(0,0,0),color]]);
		polyButton.states_([["Poly",Color.new(0,0,0),color]])
	}
	
	// Remove self from parent view
	removeSelf { |action|
		mParent.removeReserve(mName);
		mParent.sendTweet(mName,mIndex,action);
	}
	
	// removed externally from parent. This is separate because this means the call is not local and therefor we may not necessarily want to play a sound
	remove {
		tweetView.remove(true);	
	}
	
}

// ActiveTweetView is a class that that represents an actively playing tweet in the GUI
ActiveTweetView {
	
	var mName,tweetView,label,volumeSlider,deleteButton,mParent,mIndex;
	
	/*
	ActiveTweetView constructor
	@param parent Parent view of this GUI element
	@param name Name of tweet, also used as the key in the Mikrokosm 
	*/
	*new { |parent,name = 'defaultTweet',index=0|
		^super.new.init(parent,name,index);
	}
	
	init { |parent,name = 'defaultTweet',index=0|
		mParent = parent;
		mName = name;
		mIndex = index;
		
		tweetView = FlowView.new(mParent.activeView,Rect(0,0,mParent.activeView.bounds.width-18,20))
			.background_(Color.new(1,1,1,0.3));
		label = StaticText.new(tweetView,Rect(0,0,tweetView.bounds.width-142,20))
			.string_(mName)
			.font_(Font("Osaka-mono",13))
			.stringColor_(Color.new(0,0,0));
			
		/*
		volumeSlider = Slider.new(tweetView,Rect(0,0,100,20))
			.value_(0.5)
			.canFocus_(false)
			.background_(Color.new(1,1,1,0.8));
			
		deleteButton = Button.new(tweetView,Rect(0,0,20,20))
			.states_([["x",Color.new(1,1,1,0.8),Color.new(0,0,0,0.4)]])
			.action_({"Delete Not Implemented".postln})
			.canFocus_(false);
		*/
	}
	
	// Remove self from parent view
	removeSelf {
		mParent.removeActive(mName);
	}
	
	// Externally removed from parent view
	remove {
		tweetView.remove(true);	
	}
	
}